<?php
namespace Ths\Web\Util;

/**
 * 字符串处理工具类
 *
 * @link https://github.com/nette/utils
 * @author Hansen Tian <programlife555@163.com>
 * @since 1.0
 */
abstract class StringUtils
{

    /**
     * 空字符串
     *
     * @var string
     */
    const EMPTY_STRING = '';

    /**
     * 表示找到的字符串搜索索引。
     *
     * @var int
     */
    const INDEX_NOT_FOUND = -1;

    /**
     * 返回给定的字符串如果非空,否则空字符串('')。
     *
     * <pre>
     * StringUtils::nullToEmpty(null)   = ''
     * StringUtils::nullToEmpty('')     = ''
     * StringUtils::nullToEmpty(' ')    = ' '
     * StringUtils::nullToEmpty('abc')  = 'abc'
     * </pre>
     *
     * @param string $str 要检测的字符串，可以为null
     * @return string 原字符串如果非空，空字符('')如果为null
     * @since 1.0
     */
    public static function nullToEmpty($str)
    {
        return (null === $str) ? self::EMPTY_STRING : $str;
    }

    /**
     * 检测字符串如果为空或null
     *
     * <pre>
     * StringUtils::isEmpty(null)    = true
     * StringUtils::isEmpty('')      = true
     * StringUtils::isEmpty(' ')     = false
     * StringUtils::isEmpty('bob')   = false
     * StringUtils::isEmpty(' bob ') = false
     * </pre>
     *
     * @param string $str 要检测的字符串
     * @return boolean true 如果字符串为空或null
     * @see empty()
     * @since 1.0
     */
    public static function isEmpty($str)
    {
        return empty($str);
    }

    /**
     * 检测字符串如果不为空或null
     *
     * <pre>
     * StringUtils::isNotEmpty(null)      = false
     * StringUtils::isNotEmpty('')        = false
     * StringUtils::isNotEmpty(' ')       = true
     * StringUtils::isNotEmpty('bob')     = true
     * StringUtils::isNotEmpty('  bob  ') = true
     * </pre>
     *
     * @param string $str 要检测的字符串
     * @return boolean true 如果字符串不为空或null
     * @see StringUtils::isEmpty
     * @since 1.0
     */
    public static function isNotEmpty($str)
    {
        return ! self::isEmpty($str);
    }

    /**
     * 检测字符串如果为 空白字符，空或null
     *
     * <pre>
     * StringUtils::isBlank(null)      = true
     * StringUtils::isBlank('')        = true
     * StringUtils::isBlank(' ')       = true
     * StringUtils::isBlank('bob')     = false
     * StringUtils::isBlank('  bob  ') = false
     * </pre>
     *
     * @param string $str 要检测的字符串
     * @return boolean true 如果是 空白字符，空或null
     * @since 1.0
     */
    public static function isBlank($str)
    {
        return self::isEmpty(trim($str));
    }


    /**
     * 检测字符串如果不为 空白字符，空或null
     *
     * <pre>
     * StringUtils::isNotBlank(null)      = false
     * StringUtils::isNotBlank('')        = false
     * StringUtils::isNotBlank(' ')       = false
     * StringUtils::isNotBlank('bob')     = true
     * StringUtils::isNotBlank('  bob  ') = true
     * </pre>
     *
     * @param string $str 要检测的字符串
     * @return boolean true 如果不是 空白字符，空或null
     * @since 1.0
     */
    public static function isNotBlank($str)
    {
        return ! self::isBlank($str);
    }

    /**
     * 比较两个字符串是否相等
     *
     * <pre>
     *   StringUtils::equals(null, null)   = true
     *   StringUtils::equals(null, 'abc')  = false
     *   StringUtils::equals('abc', null)  = false
     *   StringUtils::equals('abc', 'abc') = true
     *   StringUtils::equals('abc', 'ABC') = false
     * </pre>
     *
     * @param string $str1 字符串1
     * @param string $str2 字符串2
     * @return boolean true 相等; 否则不相等
     * @since 1.0
     */
    public static function equals($str1, $str2)
    {
        return $str1 === null ? $str2 === null : $str1 === $str2;
    }

    /**
     * 比较两个字符串是否不相等
     *
     * @param string $str1 字符串1
     * @param string $str2 字符串2
     * @return boolean true 不相等; 否则相等
     * @see self::equals()
     */
    public static function notEquals($str1, $str2)
    {
        return ! self::equals($str1, $str2);
    }

    /**
     * 将字符串为小写按php内置函数 mb_strtolower、strtolower 顺序转换。<br>
     * 空输入字符串中返回null。
     *
     * <pre>
     * StringUtils::lowerCase(null)  = null
     * StringUtils::lowerCase('')    = ''
     * StringUtils::lowerCase('aBc') = 'abc'
     * </pre>
     *
     * @param string $str 要转换为小写的字符串，可以为null
     * @return string 小写字符串，null 如果空字符串输入
     * @since 1.0
     */
    public static function lowerCase($str)
    {
        if ($str === null) {
            return null;
        }

        if (self::EMPTY_STRING === $str) {
            return self::EMPTY_STRING;
        }

        if (function_exists('mb_strtolower')) {
            return mb_strtolower($str, 'utf-8');
        }

        return strtolower($str);
    }

    /**
     * 将字符串为大写按php内置函数 mb_strtoupper、strtoupper 顺序转换。<br>
     * 空输入字符串中返回null。
     *
     * <pre>
     * StringUtils::upperCase(null)  = null
     * StringUtils::upperCase('')    = ''
     * StringUtils::upperCase('aBc') = 'ABC'
     * </pre>
     *
     * @param string $str 要转换为大写的字符串，可以为null
     * @return string 大写字符串，null 如果空字符串输入
     * @since 1.0
     */
    public static function upperCase($str)
    {
        if ($str === null) {
            return null;
        }

        if (self::EMPTY_STRING === $str) {
            return self::EMPTY_STRING;
        }

        if (function_exists('mb_strtoupper')) {
            return mb_strtoupper($str, 'utf-8');
        }

        return strtoupper($str);
    }

    /**
     *
     *
     * @param string $str
     * @return string
     */
    public static function capitalize($str)
    {
        return ucfirst($str);
    }

    /**
     * 检测字符串是否以指定的前缀字符开始。<br>
     * 空值没有例外处理。两个null被认为是相等的。比较是区分大小写的。
     *
     * <pre>
     * StringUtils::startsWith(null, null)      = true
     * StringUtils::startsWith(null, 'abc')     = false
     * StringUtils::startsWith('abcdef', null)  = false
     * StringUtils::startsWith('abcdef', '')    = true
     * StringUtils::startsWith('abcdef', 'abc') = true
     * StringUtils::startsWith('ABCDEF', 'abc') = false
     * StringUtils::startsWith('abcdef', 'abd') = false
     * </pre>
     *
     * @param string $str 要检测的字符串，可以为null
     * @param string $prefix 前缀字符串，可以为null
     * @return boolean true 如果以前缀字符串开始, 区分大小写，或两者null
     * @since 1.0
     */
    public static function startsWith($str, $prefix)
    {
        if (null === $str && null === $prefix) {
            return true;
        }

        if (null === $str || null === $prefix) {
            return false;
        }

        return strncmp($str, $prefix, strlen($prefix)) === 0;
        //return strpos($str, $prefix) === 0;
    }

    /**
     * 功能同  StringUtils::startsWith()，只是不区分大小写。
     *
     * <pre>
     * StringUtils::startsWithIgnoreCase(null, null)      = true
     * StringUtils::startsWithIgnoreCase(null, 'abc')     = false
     * StringUtils::startsWithIgnoreCase('abcdef', null)  = false
     * StringUtils::startsWithIgnoreCase('abcdef', '')    = true
     * StringUtils::startsWithIgnoreCase('abcdef', 'abc') = true
     * StringUtils::startsWithIgnoreCase('ABCDEF', 'abc') = true
     * StringUtils::startsWithIgnoreCase('abcdef', 'abd') = false
     * </pre>
     *
     * @param string $str 要检测的字符串，可以为null
     * @param string $prefix 前缀字符串，可以为null
     * @return boolean true 如果以前缀字符串开始或两者null
     * @since 1.0
     * @see StringUtils::startsWith()
     */
    public static function startsWithIgnoreCase($str, $prefix)
    {
        if (null === $str && null === $prefix) {
            return true;
        }

        if (null === $str || null === $prefix) {
            return false;
        }

        $length = strlen($prefix);
        if (0 === $length) {
            return true;
        }

        return (self::lowerCase(substr($str, 0, $length)) === self::lowerCase($prefix));
    }

    /**
     * 检测字符串是否以指定的后缀字符结束。<br>
     * 空值没有例外处理。两个空引用被认为是相等的。比较是区分大小写的。
     *
     * <pre>
     * StringUtils::endsWith(null, null)      = true
     * StringUtils::endsWith(null, 'def')     = false
     * StringUtils::endsWith('abcdef', null)  = false
     * StringUtils::endsWith('abcdef', '')    = true
     * StringUtils::endsWith('abcdef', 'def') = true
     * StringUtils::endsWith('ABCDEF', 'def') = false
     * StringUtils::endsWith('ABCDEF', 'cde') = false
     * </pre>
     *
     * @param string $str 要检测的字符串，可以为null
     * @param string $prefix 后缀字符串，可以为null
     * @return boolean true 如果以后缀字符串结束或两者为null
     * @since 1.0
     */
    public static function endsWith($str, $prefix)
    {
        if (null === $str && null === $prefix) {
            return true;
        }

        if (null === $str || null === $prefix) {
            return false;
        }

        $length = strlen($prefix);
        if (0 === $length) {
          return true;
        }

        return (substr($str, -$length) === $prefix);
    }

    /**
     * 功能同  StringUtils::endsWith()，只是不区分大小写。
     *
     * <pre>
     * StringUtils::endsWithIgnoreCase(null, null)      = true
     * StringUtils::endsWithIgnoreCase(null, 'def')     = false
     * StringUtils::endsWithIgnoreCase('abcdef', null)  = false
     * StringUtils::endsWithIgnoreCase('abcdef', '')    = true
     * StringUtils::endsWithIgnoreCase('abcdef', 'def') = true
     * StringUtils::endsWithIgnoreCase('ABCDEF', 'def') = true
     * StringUtils::endsWithIgnoreCase('ABCDEF', 'cde') = false
     * </pre>
     *
     * @param string $str 要检测的字符串，可以为null
     * @param string $prefix 后缀字符串，可以为null
     * @return boolean true 如果以后缀字符串结束或两者为null
     * @since 1.0
     * @see StringUtils::endsWith()
     */
    public static function endsWithIgnoreCase($str, $prefix)
    {
        if (null === $str && null === $prefix) {
            return true;
        }

        if (null === $str || null === $prefix) {
            return false;
        }

        $length = strlen($prefix);
        if (0 === $length) {
            return true;
        }

        return (self::lowerCase(substr($str, -$length)) === self::lowerCase($prefix));
    }

    /**
     * 检查字符串中是否包含一个搜索的字符串，处理null。<br>
     * null字符串将返回false，空搜索字符串返回 true， 区分大小写。
     *
     * <pre>
     * StringUtils::contains(null, *)     = false
     * StringUtils::contains(*, null)     = false
     * StringUtils::contains('', '')      = true
     * StringUtils::contains('abc', '')   = true
     * StringUtils::contains('', 'a')     = false
     * StringUtils::contains('abc', 'a')  = true
     * StringUtils::contains('abc', 'z')  = false
     * StringUtils::contains('ABC', 'a')  = false
     * </pre>
     *
     * @param string $str 检测字符串，可以为null
     * @param string $search 查找字符串，可以为null
     * @return boolean true 如果字符串中包含搜索的字符串， false 如果不是或null字符串输入
     * @since 1.0
     */
    public static function contains($str, $search)
    {
        if (null === $str || null === $search) {
            return false;
        }

        if (self::EMPTY_STRING === $search) {
            return true;
        }

        return strpos($str, $search) !== false;
    }

    /**
     * 截取第一次出现搜索字符串之前的字符串，不包含搜索字符串。<br>
     * null 字符串输入将返回 null；空字符串 ("")将返回空字符串；null、空搜索字符串都将返回空字符串。<br>
     * 如果没有找到搜索字符串将返回空字符串。
     *
     * <pre>
     * StringUtils::substringBefore(null, *)        = null
     * StringUtils::substringBefore('', *)          = ''
     * StringUtils::substringBefore('abc', null)    = ''
     * StringUtils::substringBefore('abc', '')      = ''
     * StringUtils::substringBefore('abc', 'd')     = ''
     * StringUtils::substringBefore('abc', 'a')     = ''
     * StringUtils::substringBefore('abcba', 'b')   = 'a'
     * StringUtils::substringBefore('abc', 'c')     = 'ab'
     * </pre>
     *
     * @param string $str 要截取的字符串，可以为null
     * @param string $separator 搜索字符串，可以为null
     * @return string 截取第一次出现搜索字符串之前的字符串；null如果null字符串输入。
     * @since 1.0
     */
    public static function substringBefore($str, $separator)
    {
        if (self::isEmpty($str)) {
            return $str;
        }

        if (self::isEmpty($separator)) {
            return self::EMPTY_STRING;
        }

        $pos = self::pos($str, $separator, 1);
        if (self::INDEX_NOT_FOUND === $pos) {
            return self::EMPTY_STRING;
        }

        return substr($str, 0, $pos);
    }

    /**
     * 截取第一次出现搜索字符串之后的字符串，不包含搜索字符串。<br>
     * null 字符串输入将返回 null；空字符串 ("")将返回空字符串；null、空搜索字符串都将返回空字符串。<br>
     * 如果没有找到搜索字符串将返回空字符串。
     *
     * <pre>
     * StringUtils::substringAfter(null, *)        = null
     * StringUtils::substringAfter('', *)          = ''
     * StringUtils::substringAfter('abc', null)    = ''
     * StringUtils::substringAfter('abc', '')      = ''
     * StringUtils::substringAfter('abc', 'd')     = ''
     * StringUtils::substringAfter('abc', 'c')     = ''
     * StringUtils::substringAfter('abcba', 'b')   = 'cba'
     * StringUtils::substringAfter('abc', 'a')     = 'bc'
     * </pre>
     *
     * @param string $str 要截取的字符串，可以为null
     * @param string $separator 搜索字符串，可以为null
     * @return string 截取第一次出现搜索字符串之后的字符串；null如果null字符串输入。
     * @since 1.0
     */
    public static function substringAfter($str, $separator)
    {
        if (self::isEmpty($str)) {
            return $str;
        }

        if (self::isEmpty($separator)) {
            return self::EMPTY_STRING;
        }

        $pos = self::pos($str, $separator, 1);
        if (self::INDEX_NOT_FOUND === $pos) {
            return self::EMPTY_STRING;
        }

        $startPos = $pos + strlen($separator);
        if ($startPos >= strlen($str)) {
            return self::EMPTY_STRING;
        }

        return substr($str, $startPos);
    }

    /**
     * 标准化行结束到UNIX样式。
     *
     * @param string $str 要转换的字符串，可以为null
     * @return string
     * @since 1.0
     */
    public static function normalizeNewLines($str)
    {
        return str_replace(["\r\n", "\r"], "\n", $str);
    }

    /**
     * 查找字符串第n次出现的位置
     *
     * @param string $str 原字符串
     * @param string $search 要查找的字符串
     * @param int $ordinal 第几次出现
     * @return int
     */
    private static function pos($str, $search, $ordinal = 1)
    {
        if ($ordinal > 0) {
            if (strlen($search) === 0) {
                return 0;
            }

            $pos = 0;
            while (false !== ($pos = strpos($str, $search, $pos)) && --$ordinal) {
                $pos++;
            }
        } else {
            $len = strlen($str);
            if (strlen($search) === 0) {
                return $len;
            }

            $pos = $len - 1;
            while (false !== ($pos = strrpos($str, $search, $pos - $len)) && ++$ordinal) {
                $pos--;
            }
        }

        return (false === $pos) ? self::INDEX_NOT_FOUND : $pos;
    }
}